Перейти к основному содержимому

5.14. Справочник по Swift

Разработчику Архитектору

Справочник по Swift

Основы языка

1. Общие принципы языка Swift

Swift — это современный, типобезопасный, компилируемый язык программирования, разработанный Apple для создания приложений на платформах iOS, macOS, watchOS, tvOS и других. Он сочетает в себе высокую производительность, читаемость кода и мощные возможности абстракции.

Swift поддерживает:

  • Статическую типизацию
  • Вывод типов
  • Управление памятью через ARC (Automatic Reference Counting)
  • Функциональное, объектно-ориентированное и протокол-ориентированное программирование
  • Безопасность выполнения за счёт проверок на этапе компиляции

2. Синтаксис и структура программы

Программа на Swift состоит из последовательности объявлений, выражений и операторов. Точка с запятой не обязательна в конце строки, но может использоваться для разделения нескольких выражений в одной строке.

print("Hello, world!")

Это минимальная программа на Swift, выводящая текст в консоль.

3. Переменные и константы

3.1. Константы (let)

Константа — это значение, которое устанавливается один раз и не изменяется в дальнейшем.

let pi = 3.14159
let name: String = "Alice"

Тип константы может быть указан явно или выведен автоматически.

3.2. Переменные (var)

Переменная — это значение, которое может изменяться после инициализации.

var counter = 0
counter = 1
var message: String
message = "Ready"

4. Типы данных

Swift предоставляет богатую систему встроенных типов.

4.1. Целочисленные типы

  • Int — целое число со знаком, размер зависит от архитектуры (32 или 64 бита)
  • Int8, Int16, Int32, Int64 — целые числа фиксированного размера
  • UInt, UInt8, UInt16, UInt32, UInt64 — целые числа без знака

Примеры:

let age: Int = 25
let byte: UInt8 = 255

4.2. Числа с плавающей точкой

  • Float — 32-битное число с плавающей точкой
  • Double — 64-битное число с плавающей точкой (используется по умолчанию)
let height: Float = 175.5
let distance = 123.456 // тип Double

4.3. Логический тип

  • Bool — принимает значения true или false
let isActive = true
let isComplete: Bool = false

4.4. Строки и символы

  • String — последовательность символов Unicode
  • Character — одиночный символ
let greeting = "Hello"
let exclamation: Character = "!"

Строки поддерживают интерполяцию:

let name = "Bob"
let message = "Hello, \(name)!"

4.5. Кортежи (Tuples)

Кортеж — это составной тип, группирующий несколько значений.

let httpStatus = (200, "OK")
let person = (name: "Eve", age: 30)
print(person.name) // "Eve"

Кортежи могут быть именованными или безымянными.

4.6. Опционалы (Optional)

Опционал — это тип, который может содержать значение или быть nil.

var optionalName: String? = "Tim"
optionalName = nil

Распаковка опционалов:

  • Принудительная: value!
  • Опциональная привязка: if let value = optional { ... }
  • Nil-coalescing: value ?? defaultValue

5. Операторы

5.1. Арифметические

  • + — сложение
  • - — вычитание
  • * — умножение
  • / — деление
  • % — остаток от деления

5.2. Сравнения

  • ==, != — равенство и неравенство
  • <, <=, >, >= — сравнение порядка

5.3. Логические

  • && — логическое И
  • || — логическое ИЛИ
  • ! — логическое НЕ

5.4. Присваивания

  • = — простое присваивание
  • +=, -=, *=, /=, %= — составные присваивания

5.5. Тернарный условный оператор

let result = condition ? valueIfTrue : valueIfFalse

5.6. Диапазоны

  • Замкнутый: a...b — включает b
  • Полуоткрытый: a..<b — не включает b
for i in 1...5 { print(i) } // 1, 2, 3, 4, 5
for j in 0..<3 { print(j) } // 0, 1, 2

6. Коллекции

6.1. Массивы (Array)

Упорядоченный список значений одного типа.

var numbers = [1, 2, 3]
numbers.append(4)
let first = numbers[0]

Инициализация:

let emptyArray: [Int] = []
var strings = Array<String>()

6.2. Словари (Dictionary)

Коллекция пар «ключ-значение».

var capitals = ["France": "Paris", "Japan": "Tokyo"]
capitals["Germany"] = "Berlin"
let franceCapital = capitals["France"] // Optional<String>

Инициализация:

let emptyDict: [String: Int] = [:]
var scores = Dictionary<String, Int>()

6.3. Множества (Set)

Неупорядоченная коллекция уникальных значений.

var genres: Set<String> = ["Rock", "Jazz", "Pop"]
genres.insert("Blues")

Требования: элементы должны соответствовать протоколу Hashable.

7. Управляющие конструкции

7.1. Условные операторы

if / else if / else:

if temperature > 30 {
print("Hot")
} else if temperature > 10 {
print("Warm")
} else {
print("Cold")
}

switch: Поддерживает сопоставление с шаблонами, диапазонами, кортежами, опционалами.

switch statusCode {
case 200:
print("OK")
case 400...499:
print("Client error")
case let x where x >= 500:
print("Server error: \(x)")
default:
print("Unknown")
}

Каждый case должен содержать хотя бы одно исполняемое выражение или использовать break.

7.2. Циклы

for-in:

for item in collection { ... }
for i in 0..<10 { ... }
for (index, value) in array.enumerated() { ... }

while:

while condition {
// выполняется, пока условие истинно
}

repeat-while:

repeat {
// тело цикла
} while condition

8. Функции

Функция — это блок кода с именем, параметрами и возвращаемым типом.

func greet(name: String) -> String {
return "Hello, \(name)!"
}

Вызов:

let message = greet(name: "Anna")

8.1. Параметры

  • Именованные параметры: func f(label name: Type)
  • Без метки: _ name: Type
  • Значения по умолчанию: name: String = "Guest"
  • Вариадические параметры: numbers: Int...
  • In-out параметры: inout value: Type (изменяют переданную переменную)

8.2. Возврат значений

Функция может возвращать:

  • Одно значение
  • Кортеж
  • Ничего (Void или ())

8.3. Вложенные функции

Функции могут быть определены внутри других функций.

func chooseFunction(isFast: Bool) -> (Int) -> Int {
func fast(x: Int) -> Int { return x * 2 }
func slow(x: Int) -> Int { return x + 10 }
return isFast ? fast : slow
}

Пользовательские типы и объектная модель

1. Структуры (struct)

Структура — это составной тип данных, объединяющий свойства и методы. Структуры в Swift являются значимыми типами (value types): при присваивании или передаче в функцию создаётся копия.

struct Point {
var x: Double
var y: Double

func distance(from other: Point) -> Double {
let dx = x - other.x
let dy = y - other.y
return (dx * dx + dy * dy).squareRoot()
}

mutating func move(by deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}

Особенности:

  • Поддерживают свойства, методы, инициализаторы
  • Не поддерживают наследование
  • Методы, изменяющие состояние, помечаются как mutating
  • Имеют автоматически сгенерированный инициализатор-член (memberwise initializer), если не определён пользовательский

2. Классы (class)

Класс — это ссылочный тип (reference type), экземпляры которого разделяются по ссылке. Классы поддерживают наследование, переопределение методов и деструкторы.

class Vehicle {
var brand: String
var maxSpeed: Int

init(brand: String, maxSpeed: Int) {
self.brand = brand
self.maxSpeed = maxSpeed
}

func describe() -> String {
return "Vehicle: \(brand), max speed \(maxSpeed)"
}

deinit {
print("\(brand) deallocated")
}
}

Особенности:

  • Поддерживают наследование
  • Экземпляры сравниваются оператором === (тождественность ссылок)
  • Имеют один или несколько инициализаторов
  • Могут содержать деструктор (deinit)
  • Не имеют автоматического memberwise initializer

3. Инициализация

Инициализатор — это метод, создающий новый экземпляр типа.

3.1. Инициализаторы в структурах

Если не указан явно, Swift генерирует инициализатор со всеми свойствами:

let p = Point(x: 1.0, y: 2.0)

Пользовательские инициализаторы могут вызывать другие инициализаторы через self.init(...).

3.2. Инициализаторы в классах

Классы требуют, чтобы все свойства были инициализированы до завершения инициализации.

Типы инициализаторов:

  • Обозначенный (designated) — основной инициализатор класса
  • Удобный (convenience) — вторичный, должен вызывать обозначенный

Правила цепочки инициализации:

  • Обозначенный инициализатор подкласса должен вызвать обозначенный инициализатор суперкласса
  • Удобный инициализатор должен вызвать другой инициализатор в том же классе
  • Удобный инициализатор подкласса может напрямую вызвать удобный инициализатор суперкласса, если цепочка ведёт к обозначенному

Пример:

class Car: Vehicle {
var numberOfDoors: Int

init(brand: String, maxSpeed: Int, doors: Int) {
self.numberOfDoors = doors
super.init(brand: brand, maxSpeed: maxSpeed)
}

convenience init(brand: String) {
self.init(brand: brand, maxSpeed: 200, doors: 4)
}
}

4. Наследование

Подкласс наследует свойства, методы и индексы от суперкласса.

Переопределение:

  • Методы, свойства и индексы переопределяются с помощью override
  • Для запрета переопределения используется final
class ElectricCar: Car {
var batteryCapacity: Double

init(brand: String, battery: Double) {
self.batteryCapacity = battery
super.init(brand: brand, maxSpeed: 250, doors: 4)
}

override func describe() -> String {
return super.describe() + ", battery: \(batteryCapacity) kWh"
}
}

5. Перечисления (enum)

Перечисление — это тип, определяющий группу связанных значений.

enum CompassPoint {
case north, south, east, west
}

var direction = CompassPoint.north
direction = .south

5.1. Сырые значения (raw values)

Могут быть строками, символами или числовыми типами.

enum Planet: Int {
case mercury = 1, venus, earth, mars
}

let earth = Planet.earth
print(earth.rawValue) // 3

5.2. Ассоциированные значения (associated values)

Позволяют хранить дополнительные данные в каждом случае.

enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}

var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")

5.3. Методы в перечислениях

enum NetworkResponse {
case success(Data)
case failure(Error)

var isSuccess: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
}

6. Протоколы (protocol)

Протокол определяет интерфейс — набор требований к свойствам, методам и индексам.

protocol Drawable {
var area: Double { get }
func render()
}

Тип соответствует протоколу, если реализует все его требования.

struct Circle: Drawable {
var radius: Double

var area: Double {
return .pi * radius * radius
}

func render() {
print("Drawing circle with radius \(radius)")
}
}

6.1. Требования к свойствам

  • { get } — только для чтения
  • { get set } — для чтения и записи

6.2. Требования к методам

Могут быть экземплярными или статическими (static или class).

6.3. Протоколы как типы

Переменные могут иметь тип протокола:

let shapes: [Drawable] = [Circle(radius: 2), Square(side: 3)]

6.4. Расширения протоколов

Можно добавлять реализацию по умолчанию:

extension Drawable {
func logArea() {
print("Area: \(area)")
}
}

6.5. Композиция протоколов

Тип может соответствовать нескольким протоколам:

struct Player: Equatable, Codable, CustomStringConvertible {
let name: String
let score: Int

var description: String {
return "\(name): \(score)"
}

static func == (lhs: Player, rhs: Player) -> Bool {
return lhs.name == rhs.name && lhs.score == rhs.score
}
}

7. Расширения (extension)

Расширение добавляет новые функциональные возможности существующему типу — даже встроенному.

extension Int {
var squared: Int {
return self * self
}

func times(_ closure: () -> Void) {
for _ in 0..<self {
closure()
}
}
}

print(5.squared) // 25
3.times { print("Hello") }

Возможности расширений:

  • Добавление вычисляемых свойств
  • Определение новых методов
  • Реализация протоколов
  • Добавление инициализаторов (но не сохраняемых свойств)

8. Управление памятью: ARC

Swift использует Automatic Reference Counting (ARC) для управления памятью объектов классов.

  • Каждый раз, когда создаётся новая сильная ссылка (strong) на экземпляр, счётчик ссылок увеличивается
  • Когда счётчик достигает нуля, память освобождается

8.1. Сильные, слабые и несвязанные ссылки

  • strong — стандартная ссылка (по умолчанию)
  • weak — не увеличивает счётчик ссылок; всегда опциональная; становится nil, когда объект уничтожен
  • unowned — не увеличивает счётчик; не опциональная; предполагает, что объект живёт дольше

Используются для разрыва сильных циклических ссылок (retain cycles).

Пример с замыканием:

class NetworkManager {
var onComplete: (() -> Void)?

func fetchData() {
onComplete = { [weak self] in
guard let self = self else { return }
// безопасное использование self
}
}
}

9. Индексы (subscript)

Позволяют обращаться к элементам типа через квадратные скобки.

struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}

let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6]) // 18

Индексы могут иметь несколько параметров и возвращать любые типы.


Замыкания, обобщения, ошибки, асинхронность

1. Замыкания (Closures)

Замыкание — это самодостаточный блок функциональности, который может быть передан и использован в коде. Функции в Swift являются частным случаем замыканий.

1.1. Синтаксис замыканий

Полная форма:

{ (parameters) -> ReturnType in
statements
}

Пример:

let add = { (a: Int, b: Int) -> Int in
return a + b
}
print(add(2, 3)) // 5

1.2. Упрощённые формы

  • Вывод типов позволяет опустить аннотации:

    let multiply = { (x, y) in x * y }
  • Однострочные замыкания не требуют return:

    let square = { $0 * $0 }
  • Автоматические имена аргументов: $0, $1, $2...

1.3. Типы замыканий

Замыкание имеет тип (ParameterTypes) -> ReturnType.

Примеры:

let handler: () -> Void = { print("Done") }
let transformer: (String) -> Int = { $0.count }

1.4. Escape-замыкания

По умолчанию замыкания не escaping — они вызываются до завершения функции. Если замыкание сохраняется для вызова позже (например, в свойстве или асинхронной операции), оно помечается как @escaping.

var completionHandlers: [() -> Void] = []

func addHandler(_ handler: @escaping () -> Void) {
completionHandlers.append(handler)
}

1.5. Autoclosure

@autoclosure автоматически оборачивает выражение в замыкание. Используется для отложенного вычисления.

func assert(_ condition: @autoclosure () -> Bool, _ message: String) {
if !condition() {
print("Assertion failed: \(message)")
}
}

assert(2 + 2 == 5, "Math is broken")

2. Обобщённое программирование (Generics)

Обобщения позволяют писать гибкий, многоразовый код, не привязанный к конкретным типам.

2.1. Обобщённые функции

func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}

var x = 5, y = 10
swapValues(&x, &y)

<T> — параметр типа. Может быть несколько: <T, U, V>.

2.2. Обобщённые типы

struct Stack<Element> {
private var items: [Element] = []

mutating func push(_ item: Element) {
items.append(item)
}

mutating func pop() -> Element? {
return items.popLast()
}
}

var stringStack = Stack<String>()
stringStack.push("Hello")

2.3. Ограничения типов (where)

Можно накладывать требования на обобщённые типы:

func isEqual<T: Equatable>(_ a: T, _ b: T) -> Bool {
return a == b
}

// Или через where:
func merge<T>(_ array1: [T], _ array2: [T]) -> [T] where T: Comparable {
return (array1 + array2).sorted()
}

Распространённые ограничения:

  • Equatable — поддержка ==
  • Comparable — поддержка <, >
  • Hashable — возможность использования в Set и Dictionary
  • Codable — поддержка сериализации

2.4. Обобщённые расширения

extension Array where Element: Numeric {
var sum: Element {
return reduce(0, +)
}
}

let numbers = [1, 2, 3, 4]
print(numbers.sum) // 10

3. Управление ошибками

Swift использует систему бросания и перехвата ошибок на основе протокола Error.

3.1. Определение ошибок

enum NetworkError: Error {
case invalidURL
case noData
case decodingFailed
}

3.2. Функции, которые могут выбрасывать ошибки

Помечаются как throws:

func fetchData(from url: String) throws -> Data {
guard let validURL = URL(string: url) else {
throw NetworkError.invalidURL
}
// ... имитация загрузки
throw NetworkError.noData
}

3.3. Обработка ошибок

  • do-catch:

    do {
    let data = try fetchData(from: "https://example.com")
    } catch NetworkError.invalidURL {
    print("Bad URL")
    } catch {
    print("Unexpected error: \(error)")
    }
  • try? — преобразует ошибку в опционал:

    let data = try? fetchData(from: "...")
    // data: Data?
  • try! — подавляет ошибку (опасно):

    let data = try! fetchData(from: "...") // аварийное завершение при ошибке

3.4. Передача ошибок

Функция, вызывающая throws-функцию, также должна быть throws или обрабатывать ошибку.

4. Асинхронное программирование (async/await)

Начиная с Swift 5.5, язык поддерживает нативную асинхронность.

4.1. Асинхронные функции

Помечаются как async:

func fetchUser(id: Int) async throws -> User {
// имитация сетевого запроса
return User(id: id, name: "User \(id)")
}

4.2. Вызов асинхронных функций

Используется await:

Task {
do {
let user = try await fetchUser(id: 42)
print(user.name)
} catch {
print("Failed to load user")
}
}

await приостанавливает выполнение текущей задачи, не блокируя поток.

4.3. Параллельное выполнение

  • Последовательно:

    let user1 = try await fetchUser(id: 1)
    let user2 = try await fetchUser(id: 2)
  • Параллельно:

    async let u1 = fetchUser(id: 1)
    async let u2 = fetchUser(id: 2)
    let (user1, user2) = try await (u1, u2)

4.4. Задачи (Task)

Task — это единица асинхронной работы.

let task = Task {
await someAsyncWork()
}
// task.cancel() — отмена

5. Акторы (Actor)

Актор — это ссылочный тип, обеспечивающий безопасность данных в многопоточной среде. Каждый актор имеет собственную очередь выполнения.

actor BankAccount {
private var balance: Double = 0

init(initialBalance: Double) {
balance = initialBalance
}

func deposit(_ amount: Double) {
balance += amount
}

func withdraw(_ amount: Double) throws {
guard amount <= balance else {
throw BankError.insufficientFunds
}
balance -= amount
}

nonisolated func description() -> String {
return "BankAccount"
}
}

Особенности:

  • Все mutable свойства и методы изолированы внутри актора
  • Доступ к ним из внешнего кода требует await
  • nonisolated — методы, не обращающиеся к изолированным данным, могут вызываться без await

Использование:

let account = BankActor(initialBalance: 100)
await account.deposit(50)

Акторы предотвращают гонки данных (data races).

6. Работа с памятью: продвинутые аспекты

6.1. Сильные циклы (retain cycles)

Возникают, когда два объекта удерживают друг друга через сильные ссылки.

Решение:

  • Использовать weak или unowned в замыканиях и делегатах
  • Пример делегата:
    protocol DataReceiver: AnyObject {
    func receive(data: Data)
    }

    class DataManager {
    weak var receiver: DataReceiver?
    }

6.2. Weak vs Unowned

  • weak: используется, когда ссылка может стать nil (например, делегат)
  • unowned: используется, когда гарантируется, что объект живёт дольше (например, закрытие в контроллере)

Неправильное использование unowned приводит к аварийному завершению.

6.3. Утечки памяти в замыканиях

class ViewController {
var onClose: (() -> Void)?

func setup() {
onClose = { [weak self] in
self?.dismiss()
}
}
}

Без [weak self] замыкание удерживает self, создавая цикл.


Стандартная библиотека и повседневные инструменты

1. Коллекции: углублённое использование

1.1. Массивы (Array)

Инициализация:

let empty = [Int]()
let repeated = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0]
let fromRange = Array(1...5) // [1, 2, 3, 4, 5]

Основные операции:

  • append(_:), append(contentsOf:)
  • insert(_:at:), remove(at:), removeLast()
  • first, last — опциональные значения
  • isEmpty, count

Изменение порядка:

  • reverse(), reversed() — изменяет или возвращает копию
  • sort(), sorted(by:) — сортировка на месте или с возвратом

Поиск:

  • first(where:), last(where:)
  • contains(where:)
  • index(of:) — устаревший; вместо него firstIndex(where:)

Преобразования:

  • map(transform:) — преобразует каждый элемент
  • compactMap(transform:) — фильтрует nil при преобразовании опционалов
  • flatMap(transform:) — применяет map, затем «выравнивает» вложенные коллекции

Фильтрация и разбиение:

  • filter(includeElement:)
  • partition(by:) — перемещает элементы, удовлетворяющие условию, в конец
  • split(separator:maxSplits:omittingEmptySubsequences:) — для массивов символов

Свёртка:

  • reduce(initialResult:_:) — накапливает значение
  • reduce(into:_:) — эффективнее для мутабельных значений (например, словарей)

Пример:

let words = ["apple", "banana", "cherry"]
let lengths = words.map { $0.count } // [5, 6, 6]
let total = lengths.reduce(0, +) // 17

1.2. Словари (Dictionary)

Инициализация:

let dict: [String: Int] = [:]
let fromPairs = Dictionary(uniqueKeysWithValues: [("a", 1), ("b", 2)])

Обновление:

  • dict[key] = value — добавление или обновление
  • dict.updateValue(_:forKey:) — возвращает старое значение как Optional

Удаление:

  • dict[key] = nil
  • dict.removeValue(forKey:) — возвращает удалённое значение

Итерация:

for (key, value) in dict {
print("\(key): \(value)")
}

Группировка:

let grouped = words.grouped(by: { $0.first! })
// ["a": ["apple"], "b": ["banana"], "c": ["cherry"]]

Объединение:

let merged = dict1.merging(dict2) { current, _ in current }
// сохраняет значение из dict1 при конфликте

1.3. Множества (Set)

Операции над множествами:

  • union(_:) — объединение
  • intersection(_:) — пересечение
  • symmetricDifference(_:) — симметрическая разность
  • subtracting(_:) — разность

Проверки:

  • isSubset(of:), isSuperset(of:)
  • isDisjoint(with:) — не имеют общих элементов

Пример:

let primes: Set = [2, 3, 5, 7]
let odds: Set = [1, 3, 5, 7, 9]
let common = primes.intersection(odds) // [3, 5, 7]

2. Строки и символы

2.1. Индексы и подстроки

Строки в Swift не поддерживают целочисленную индексацию из-за Unicode.

let greeting = "Hello"
let index = greeting.index(greeting.startIndex, offsetBy: 1)
let char = greeting[index] // "e"

let substring = greeting[greeting.startIndex..<index] // "H"

Полезные свойства:

  • startIndex, endIndex
  • indices — диапазон всех допустимых индексов

2.2. Поиск и замена

  • contains("text")
  • hasPrefix("He"), hasSuffix("lo")
  • replacingOccurrences(of:with:)

2.3. Разделение

let components = "a,b,c".split(separator: ",") // [Substring]
let strings = components.map(String.init)

2.4. Регулярные выражения (Swift 5.7+)

import RegexBuilder

let regex = Regex {
OneOrMore(.digit)
"."
OneOrMore(.digit)
}

if let match = "Version 1.2.3".firstMatch(of: regex) {
print(match.0) // "1.2"
}

Или через литералы (Swift 5.7+):

if let match = "123-456" firstMatch /(\d+)-(\d+)/ {
print(match.1, match.2) // "123", "456"
}

3. Кодирование и сериализация

3.1. Codable

Протокол Codable объединяет Encodable и Decodable.

struct User: Codable {
var name: String
var age: Int
}

Кодирование в JSON:

let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let data = try encoder.encode(user)
let json = String(data: data, encoding: .utf8)

Декодирование:

let decoder = JSONDecoder()
let user = try decoder.decode(User.self, from: jsonData)

3.2. Настройка ключей

struct Response: Codable {
var userID: Int
var userName: String

enum CodingKeys: String, CodingKey {
case userID = "id"
case userName = "name"
}
}

3.3. Вложенные структуры

Поддерживается автоматически:

{
"user": { "name": "Alice" }
}
struct Wrapper: Codable {
var user: User
}

4. Работа с файлами и путями (через Foundation)

Хотя Swift сам по себе не содержит файловой системы, в связке с Foundation доступны мощные инструменты.

4.1. URL — основной тип для путей

let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documents.appendingPathComponent("data.json")

4.2. Чтение и запись

// Запись
try jsonData.write(to: fileURL)

// Чтение
let loadedData = try Data(contentsOf: fileURL)

4.3. Проверка существования

if FileManager.default.fileExists(atPath: fileURL.path) {
// файл существует
}

5. Локализация и интернационализация

5.1. LocalizedStringKey

В SwiftUI используется автоматическая локализация через Text("Hello").

В обычном Swift:

let message = NSLocalizedString("greeting", comment: "Welcome message")

Файл Localizable.strings:

"greeting" = "Здравствуйте!";

5.2. Форматирование дат и чисел

Через Foundation:

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
let formatted = formatter.string(from: 12345 as NSNumber) // "12,345"

6. Вспомогательные утилиты

6.1. Таймеры и задержки

Через DispatchQueue:

DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
print("Delayed execution")
}

6.2. Генерация случайных значений

Int.random(in: 1...100)
Bool.random()
Double.random(in: 0..<1)

Расширение для массива:

extension Collection {
func randomElement() -> Element? {
guard !isEmpty else { return nil }
let index = Int.random(in: startIndex..<endIndex)
return self[index]
}
}

6.3. Кэширование и ленивые вычисления

  • lazy — откладывает вычисления до момента использования:

    let squares = (1...1000).lazy.map { $0 * $0 }.prefix(5)
  • LazySequence и LazyCollection — экономят память и время

7. Паттерны проектирования в Swift

7.1. Singleton

class NetworkManager {
static let shared = NetworkManager()
private init() {}
}

7.2. Builder

Используется для сложных инициализаций:

struct URLRequestBuilder {
var url: URL?
var method: String = "GET"
var headers: [String: String] = [:]

func setURL(_ url: URL) -> Self {
var copy = self
copy.url = url
return copy
}

func build() throws -> URLRequest {
guard let url = url else { throw URLError(.badURL) }
var request = URLRequest(url: url)
request.httpMethod = method
request.allHTTPHeaderFields = headers
return request
}
}

7.3. Observer через замыкания

class ValueHolder<T> {
private var _value: T
private var observers: [(T) -> Void] = []

init(_ value: T) {
self._value = value
}

var value: T {
didSet {
observers.forEach { $0(value) }
}
}

func observe(_ handler: @escaping (T) -> Void) {
observers.append(handler)
}
}

Инструменты, практики и экосистема

1. Модульность и организация кода

1.1. Модули

Каждый фреймворк или исполняемый файл в Swift представляет собой модуль. Модуль — это единое пространство имён, содержащее типы, функции и расширения.

  • Имя модуля совпадает с именем цели (target) в Xcode
  • Доступ к публичным элементам другого модуля осуществляется через import
import Foundation
import UIKit

1.2. Уровни доступа

Swift предоставляет пять уровней доступа:

УровеньОписание
openДоступен из любого модуля; может быть подклассом и переопределён вне модуля
publicДоступен из любого модуля; не может быть подклассом/переопределён вне модуля
internalДоступен только внутри модуля (по умолчанию)
fileprivateДоступен только в пределах одного файла
privateДоступен только в пределах ближайшей области видимости (например, структуры)

Пример:

public class APIManager {
private let session = URLSession.shared
internal func fetchData() { ... }
}

1.3. Разделение кода на файлы

Рекомендуется:

  • Один основной тип на файл (класс, структура, перечисление)
  • Вспомогательные типы (например, CodingKeys) могут находиться в том же файле
  • Расширения, реализующие протоколы, выносятся в отдельные файлы: User+Codable.swift, Array+Utilities.swift

2. Тестирование

2.1. Unit-тесты (XCTest)

Создаются в отдельной цели тестирования. Каждый тест — метод, начинающийся с test.

import XCTest
@testable import MyApp

class UserManagerTests: XCTestCase {
func testUserCreation() {
let user = User(name: "Alice", age: 30)
XCTAssertEqual(user.name, "Alice")
XCTAssertTrue(user.isActive)
}

func testInvalidAgeThrowsError() throws {
XCTAssertThrowsError(try User(name: "Bob", age: -5)) { error in
XCTAssertEqual(error as? UserError, .invalidAge)
}
}
}
  • @testable import позволяет получать доступ к internal элементам
  • Используются утверждения: XCTAssertEqual, XCTAssertTrue, XCTAssertThrowsError и др.

2.2. Асинхронные тесты

func testAsyncFetch() async throws {
let manager = NetworkManager()
let user = try await manager.fetchUser(id: 42)
XCTAssertEqual(user.id, 42)
}

Тест автоматически ожидает завершения асинхронной операции.

2.3. Mock-объекты

Для изоляции зависимостей создаются заглушки:

protocol NetworkService {
func fetchUser(id: Int) async throws -> User
}

class MockNetworkService: NetworkService {
var shouldFail = false
func fetchUser(id: Int) async throws -> User {
if shouldFail { throw NetworkError.timeout }
return User(id: id, name: "Mock")
}
}

3. Документирование кода

Swift поддерживает генерацию документации через комментарии.

3.1. Синтаксис

/// Загружает пользователя по идентификатору.
///
/// Эта функция выполняет сетевой запрос к API и возвращает объект `User`.
/// При ошибке сети или недопустимом ответе выбрасывается `NetworkError`.
///
/// - Parameters:
/// - id: Уникальный идентификатор пользователя. Должен быть положительным.
/// - Returns: Объект `User`, содержащий данные профиля.
/// - Throws: `NetworkError.invalidID`, если `id <= 0`.
///
/// - Important: Вызов этой функции требует активного интернет-соединения.
public func fetchUser(id: Int) throws -> User {
// ...
}

Поддерживаемые ключевые слова:

  • - Parameters:
  • - Returns:
  • - Throws:
  • - Important:, - Note:, - Warning:
  • - SeeAlso:, - Since:, - Version:

3.2. Генерация документации

Инструменты:

  • Xcode Quick Help — отображает документацию при наведении
  • SourceDocs — генерирует HTML-документацию
  • Jazzy — популярный генератор документации в стиле Apple

4. Инструменты разработки

4.1. Swift Package Manager (SPM)

Встроенный менеджер зависимостей и сборки.

Файл Package.swift:

let package = Package(
name: "MyLibrary",
platforms: [.iOS(.v15)],
products: [
.library(name: "MyLibrary", targets: ["MyLibrary"])
],
dependencies: [
.package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0")
],
targets: [
.target(
name: "MyLibrary",
dependencies: ["Algorithms"]
)
]
)

4.2. Linter и форматирование

  • SwiftLint — проверяет стиль кода и потенциальные ошибки
  • SwiftFormat — автоматически форматирует код

Пример правила .swiftlint.yml:

disabled_rules:
- force_cast
- line_length
opt_in_rules:
- empty_count

4.3. Отладка

  • print() — базовый вывод
  • #file, #line, #function — макросы для логирования:
    func log(_ message: String, file: String = #file, line: Int = #line) {
    print("[\(file.components(separatedBy: "/").last!):\(line)] \(message)")
    }
  • os_log — производительный системный логгер (через OSLog)

5. Производительность и оптимизация

5.1. Выбор между struct и class

  • Используйте struct по умолчанию — они быстрее, безопаснее и не создают retain cycles
  • Используйте class, когда требуется:
    • Наследование
    • Ссылочная семантика (разделяемое состояние)
    • Делегирование с weak

5.2. Избегание копирования больших структур

Если структура содержит много данных, передача по значению может быть дорогой. В таких случаях:

  • Используйте inout для мутации
  • Рассмотрите class или actor
  • Используйте Copy-on-Write (CoW) вручную через isKnownUniquelyReferenced

5.3. Lazy properties

Откладывают инициализацию до первого обращения:

lazy var expensiveObject = computeExpensiveValue()

5.4. Использование final

Пометка класса как final позволяет компилятору оптимизировать вызовы методов:

final class Utility {
func helper() { ... }
}

5.5. Измерение производительности

func measure<T>(_ block: () throws -> T) rethrows -> T {
let start = CFAbsoluteTimeGetCurrent()
let result = try block()
let time = CFAbsoluteTimeGetCurrent() - start
print("Execution time: \(time)s")
return result
}

6. Безопасность кода

6.1. Избегание принудительного развёртывания

Плохо:

let value = optional!

Хорошо:

if let value = optional {
// безопасное использование
}
// или
guard let value = optional else { return }

6.2. Проверка границ

Используйте безопасные расширения:

extension Collection {
subscript(safe index: Index) -> Element? {
return indices.contains(index) ? self[index] : nil
}
}

6.3. Обработка ошибок вместо fatalError

Избегайте аварийного завершения в production-коде. Используйте Result, Optional или выбрасывание ошибок.

7. Лучшие практики стиля и читаемости

7.1. Именование

  • Типы (struct, class, enum, protocol) — UpperCamelCase
  • Переменные, функции, свойства — lowerCamelCase
  • Константы — lowerCamelCase (не UPPER_CASE)
  • Булевы свойства — префиксы is, has, can: isEnabled, hasData

7.2. Длина строки

Рекомендуется ограничивать строку 100–120 символами.

7.3. Комментарии

  • Комментарии объясняют «почему», а не «что»
  • Самодокументируемый код предпочтительнее комментариев

7.4. Функции

  • Короткие, одноцелевые функции
  • Минимум побочных эффектов
  • Чистые функции (без состояния) предпочтительны

7.5. Расширения

Группируйте логически связанный код:

// MARK: - Codable
extension User: Codable { ... }

// MARK: - Equatable
extension User: Equatable { ... }

8. Экосистема и совместимость

8.1. Версии Swift

  • Swift стремится к обратной совместимости
  • Новые возможности помечаются как @available(iOS 15.0, *)
  • Используйте условную компиляцию для поддержки старых версий:
if #available(iOS 15.0, *) {
await someAsyncFunction()
} else {
// fallback
}

8.2. Кросс-платформенность

Swift работает на:

  • Apple-платформах (iOS, macOS, watchOS, tvOS)
  • Linux (через Swift.org)
  • Windows (экспериментально)

Код, не зависящий от Foundation или UIKit, может быть переносимым.

8.3. Интеграция с Objective-C

  • Классы Swift, наследующие NSObject, могут быть доступны в Objective-C
  • Используйте @objc и @objcMembers для экспорта
  • Не все возможности Swift доступны в Objective-C (например, generics, кортежи)